package com.huterox.todoscheduler.core.suports;

import com.huterox.todoscheduler.annotate.*;
import com.huterox.todoscheduler.common.BeanNameConvert;
import com.huterox.todoscheduler.core.beans.BeanDefinition;
import com.huterox.todoscheduler.core.wapper.BeanWrapper;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TodoApplicationContext implements Serializable {

    private BeanDefinitionReader reader;
    private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    private final Map<String, BeanWrapper> factoryBeanInstanceCache = new HashMap<>();
    private final Map<String, Object> factoryBeanObjectCache = new HashMap<>();
    //记录一下谁依赖了谁，在创建过程当中，还没有被创建的话,表示：B被A依赖，但是此时B没有完成实例
    private final Map<String, List<String>> factoryBeanPopulateCache = new HashMap<>();

    public TodoApplicationContext() {

        //加载配置文件读取器
        this.reader = new BeanDefinitionReader();
        List<BeanDefinition> beanDefinitions = this.reader.doLoadBeanDefinitions();
        //2.缓存beanDefinitions对象
        try {
            doRegistryBeanDefinition(beanDefinitions);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //3.创建IOC容器,把类都放在IOC容器里面
        doCreateBean();
    }

    private void doCreateBean() {
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            //非延时加载
            getBean(beanName);
        }
    }

    private void doRegistryBeanDefinition(List<BeanDefinition> beanDefinitions) throws Exception {

        for (BeanDefinition beanDefinition : beanDefinitions) {
            //双键存储便于双向查找,把bean的名字和对应的包名放在一起
            if (this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("The " + beanDefinition.getFactoryBeanName() + " is exists!");
            }
            this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
            this.beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
        }
    }

    //创建Bean的实例
    public Object getBean(String beanName) {
        //得到对象的全包名,当然这个信息封装在beanDefinition里面
        BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        //实例化，这个的话是先进行基本地无参构造实例化对象，此时是半成品
        Object instance = instanceBean(beanName, beanDefinition);
        if (instance == null) return null;
        //实例封装为beanWrapper当中
        BeanWrapper beanWrapper = new BeanWrapper(instance);
        //将这个wrapper对象放在IOC容器里面,允许提取暴露
        this.factoryBeanInstanceCache.put(beanName, beanWrapper);
        //完成依赖注入
        populateBean(beanWrapper);
        return this.factoryBeanInstanceCache.get(beanName).getWrapperInstance();
    }

    private void populateBean(BeanWrapper beanWrapper){
        //开始做依赖注入给值
        Object instance = beanWrapper.getWrapperInstance();
        Class<?> clazz = beanWrapper.getWrapperClass();
        Field[] fields = clazz.getDeclaredFields();
        String beanName = BeanNameConvert.toLowerFirstCase(clazz.getSimpleName());
        //遍历所有的字段，看看有木有需要依赖注入的
        for (Field field : fields) {
            if (!(field.isAnnotationPresent(TodoAutowired.class))) continue;
            TodoAutowired autowired = field.getAnnotation(TodoAutowired.class);
            String autowiredBeanName = autowired.value().trim();
            if ("".equals(autowiredBeanName)) {
                autowiredBeanName = field.getType().getName();
            }
            //打开权限
            field.setAccessible(true);
            try {
                if (!this.factoryBeanInstanceCache.containsKey(autowiredBeanName)){
                    //说明现在这个容器当中，依赖的那个家伙还没有实例化
                    if(this.factoryBeanPopulateCache.containsKey(autowiredBeanName)){
                        List<String> needings = this.factoryBeanPopulateCache.get(autowiredBeanName);
                        needings.add(beanName);
                    }else {
                        List<String> needings = new ArrayList<>();
                        needings.add(beanName);
                        this.factoryBeanPopulateCache.put(autowiredBeanName,needings);
                    }
                    continue;
                }
                //这个就是为什么要按照标准来写首字母要小写
                field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());

            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        //顺便检测当前的bean有没有在创建之前被依赖的情况，有的话补偿一下
        if(this.factoryBeanPopulateCache.containsKey(beanName)){
            for (String populateBeanName:this.factoryBeanPopulateCache.get(beanName)){
                Object instanceNeed = this.factoryBeanInstanceCache.get(populateBeanName).getWrapperInstance();
                Class<?> clazzNeed = this.factoryBeanInstanceCache.get(populateBeanName).getWrapperClass();
                try {
                    Field declaredField = clazzNeed.getDeclaredField(beanName);
                    declaredField.setAccessible(true);
                    declaredField.set(instanceNeed,instance);
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    /**
     * 把打了TodoComponent，TodoService，TodoService的类放在我们的容器里面
     * 并且这里进行无参构造，也就是初步实例化
     * 后面我们再把这些玩意的方法进行解析
    * */
    private Object instanceBean(String beanName, BeanDefinition beanDefinition) {

        String className = beanDefinition.getBeanClassName();
        Object instance = null;
        try {
            Class<?> clazz = Class.forName(className);
            if (!(  clazz.isAnnotationPresent(TodoComponent.class) ||
                    clazz.isAnnotationPresent(TodoService.class)   ||
                    clazz.isAnnotationPresent(TodoController.class)||
                    clazz.isAnnotationPresent(TodoList.class))
            ) {
                return null;
            }
            //先进行初步得无参构造,然后放到这个factoryBeanObjectCache当中去
            instance = clazz.newInstance();
            this.factoryBeanObjectCache.put(beanName, instance);

        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return instance;
    }

    //重载按照这个类的类型来创建
    public Object getBean(Class<?> className) {
        return getBean(className.getName());
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }

    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[0]);
    }

    public BeanDefinitionReader getReader() {
        return reader;
    }

    public void setReader(BeanDefinitionReader reader) {
        this.reader = reader;
    }

    public Map<String, BeanDefinition> getBeanDefinitionMap() {
        return beanDefinitionMap;
    }

    public Map<String, BeanWrapper> getFactoryBeanInstanceCache() {
        return factoryBeanInstanceCache;
    }

    public Map<String, Object> getFactoryBeanObjectCache() {
        return factoryBeanObjectCache;
    }
}
